﻿using System.Collections.Specialized;
using System.Web;
using Com.FastSearch.SharePoint.WebControls.ESPSearchWebParts.UI.Client;
using Microsoft.SharePoint.Utilities;

namespace Com.FastSearch.SharePoint.WebControls.ESPSearchWebParts.UI
{
    /// <summary>
    /// Helper class for manipulating URLs by adding, setting or removing parameters.
    /// </summary>
    public class NavigationHelper
    {
        private NavigationHelper()
        {
            // Static helper class. Prevent the compiler from generating a default constructor.
        }

        /// <summary>
        /// <para> Sets the specified request parameter to the given value, replacing any
        /// previous value(s) if present.</para>
        /// <para>If the provided search group is not the default search group, the parameter's
        /// name will be augmented with group information. The parameter's value will be
        /// url-encoded. Will update the provided <see cref="NavigationHelperParameter"/>
        /// object, as well as return the resulting url.</para>
        /// </summary>
        /// <param name="navParam">the navigation state</param>
        /// <param name="parameterName">the name of the parameter to set</param>
        /// <param name="parameterValue">the value the parameter will be set to</param>
        /// <param name="ESPSearchGroupID">the search group for which the parameter should be set</param>
        /// <returns>the resulting url</returns>
        public static string SetParameter(NavigationHelperParameter navParam, string parameterName, string parameterValue, ESPSearchGroupEnum ESPSearchGroupID)
        {
            return DoParameter(navParam, parameterName, parameterValue, ESPSearchGroupID, false, false);
        }

        /// <summary>
        /// <para>Adds the specified parameter to the request, possibly resulting in multiple
        /// parameters with the same name (which is valid, and will result in an array being
        /// available on the server-side).</para>
        /// <para>If the provided search group is not the default search group, the parameter's
        /// name will be augmented with group information. The parameter's value will be
        /// url-encoded. Will update the provided <see cref="NavigationHelperParameter"/>
        /// object, as well as return the resulting url.</para>
        /// </summary>
        /// <param name="navParam">the navigation state</param>
        /// <param name="parameterName">the name of the parameter to add</param>
        /// <param name="parameterValue">the value of the parameter to add</param>
        /// <param name="ESPSearchGroupID">the search group for which the parameter should be added</param>
        /// <returns>the resulting url</returns>
        public static string AddParameter(NavigationHelperParameter navParam, string parameterName, string parameterValue, ESPSearchGroupEnum ESPSearchGroupID)
        {
            return DoParameter(navParam, parameterName, parameterValue, ESPSearchGroupID, true, false);
        }

        /// <summary>
        /// <para>Removes all parameters with the specified name from the request.</para>
        /// <para>If the provided search group is not the default search group, the parameter's
        /// name will be augmented with group information. Will update the provided
        /// <see cref="NavigationHelperParameter"/> object, as well as return the resulting
        /// url.</para>
        /// </summary>
        /// <param name="navParam">the navigation state</param>
        /// <param name="parameterName">the name of the parameter to add</param>
        /// <param name="ESPSearchGroupID">the search group for which the parameter should be removed</param>
        /// <returns>the resulting url</returns>
        public static string RemoveParameter(NavigationHelperParameter navParam, string parameterName, ESPSearchGroupEnum ESPSearchGroupID)
        {
            return DoParameter(navParam, parameterName, null, ESPSearchGroupID, true, true);
        }

        /// <summary>
        /// <para>Removes the parameter with the specified name and value from the
        /// request.</para>
        /// <para>If the provided search group is not the default search group, the parameter's
        /// name will be augmented with group information. The parameter's value will be
        /// url-encoded. Will update the provided <see cref="NavigationHelperParameter"/>
        /// object, as well as return the resulting url.</para>
        /// </summary>
        /// <param name="navParam">the navigation state</param>
        /// <param name="parameterName">the name of the parameter to remove</param>
        /// <param name="parameterValue">the value of the parameter to remove</param>
        /// <param name="ESPSearchGroupID">the search group for which the parameter should be removed</param>
        /// <returns>the resulting url</returns>
        public static string RemoveParameter(NavigationHelperParameter navParam, string parameterName, string parameterValue, ESPSearchGroupEnum ESPSearchGroupID)
        {
            return DoParameter(navParam, parameterName, parameterValue, ESPSearchGroupID, true, true);
        }

        /// <summary>
        /// The internal method that does all the work, whether it's adding, setting or removing
        /// a parameter.
        /// </summary>
        /// <param name="param">the navigation state that should be manipulated</param>
        /// <param name="parameterName">the name of the parameter</param>
        /// <param name="parameterValue">the value of the parameter, null if not important (i.e. remove all with specified name)</param>
        /// <param name="ESPSearchGroupID">the search group the parameter is for</param>
        /// <param name="allowMultiple">whether multiple parameters with the same value are accepted; using false will replace parameters instead of adding them</param>
        /// <param name="remove">whether the parameter should be removed instead of added or set</param>
        /// <returns>the resulting url</returns>
        private static string DoParameter(NavigationHelperParameter param, string parameterName, string parameterValue, ESPSearchGroupEnum ESPSearchGroupID, bool allowMultiple, bool remove)
        {
            string resultUrl = param.RawUrl;
            string paramNameWithPostfix = parameterName + SearchRequest.GetPropertyPostfixForGroup(ESPSearchGroupID);
            string encodedValue = SPEncode.UrlEncode(parameterValue);
            string encodedPair = paramNameWithPostfix + '=' + encodedValue;
            char separator = param.QueryString.HasKeys() ? '&' : '?';

            if ((param.QueryString[paramNameWithPostfix] == null || allowMultiple) && !remove)
            {
                resultUrl += separator + encodedPair;
                param.QueryString.Add(paramNameWithPostfix, parameterValue);
            }
            else
            {
                string[] keys = param.QueryString.AllKeys;                
                separator = '?';
                resultUrl = param.FilePath;
                foreach (string key in keys)
                {
                    // Can actually happen if the url contains &&
                    if (key != null)
                    {
                        if (key.Equals(paramNameWithPostfix))
                        {
                            if (remove)
                            {
                                if (parameterValue == null)
                                {
                                    param.QueryString.Remove(key);
                                }
                                else
                                {
                                    string[] values = param.QueryString.GetValues(key);
                                    param.QueryString.Remove(key);
                                    foreach (string value in values)
                                    {
                                        if (value != parameterValue)
                                        {
                                            param.QueryString.Add(key, value);
                                            resultUrl += separator + key + '=' + SPEncode.UrlEncode(value);
                                            separator = '&';
                                        }
                                    }
                                }
                            }
                            else
                            {
                                resultUrl += separator + encodedPair;
                                param.QueryString[key] = parameterValue;
                                separator = '&';
                            }
                        }
                        else
                        {
                            foreach (string value in param.QueryString.GetValues(key))
                            {
                                resultUrl += separator + key + '=' + SPEncode.UrlEncode(value);
                                separator = '&';
                            }
                        }
                    }
                }
            }
            param.RawUrl = resultUrl;
            return resultUrl;
        }
    }

    /// <summary>
    /// Class that encapsulates http navigation state; url, parameters, file path.
    /// </summary>
    public class NavigationHelperParameter
    {
        private string _rawUrl;
        private readonly NameValueCollection _queryUrl;
        private readonly string _filePath;

        /// <summary>
        /// Create a navigation helper parameter based on the given http-request.
        /// </summary>
        /// <param name="request">the http-request</param>
        public NavigationHelperParameter(HttpRequest request)
            : this(request.RawUrl, request.QueryString, request.FilePath)
        {
        }

        /// <summary>
        /// Create a navigation helper parameter with the specified values.
        /// </summary>
        /// <param name="rawUrl">the raw url</param>
        /// <param name="QueryString">the query parameters</param>
        /// <param name="FilePath">the path to the requested file/action</param>
        public NavigationHelperParameter(string rawUrl, NameValueCollection QueryString, string FilePath)
        {
            _rawUrl = rawUrl;
            _queryUrl = new NameValueCollection(QueryString);
            _filePath = FilePath;
        }

        /// <summary>
        /// The raw url of the request.
        /// </summary>
        public string RawUrl
        {
            get { return _rawUrl; }
            set { _rawUrl = value; }
        }

        /// <summary>
        /// The query parameters. Possible with multiple values for a single parameters, can be
        /// retrieved as an array by calling GetValues(string key) on the NameValueCollection.
        /// </summary>
        public NameValueCollection QueryString
        {
            get { return _queryUrl; }
        }

        /// <summary>
        /// The path to the requested file/action.
        /// </summary>
        public string FilePath
        {
            get { return _filePath; }
        }
    }
}
